﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Newtonsoft.Json.Linq;

using ProjectBase.Core;
using ProjectBase.Mvc;

using $prjsn$.Entity;
using $prjsn$.Common;

namespace $safeprojectname$
{
    public class AdditionalArrayValueProvider<TEntity> : EntityArrayValueProvider<TEntity, int>
        where TEntity : class, IBusinessObject
    {
        #region Read
        public override object GetValue(object target)
        {
            var result = new JObject();

            var additional = PropertyInfo.GetValue(target, null) as IDictionary;

            if (additional.IsNull() || additional.Count == 0)
            {
                return result;
            }

            // the MetaData Attribute for current property
            var meta = Attributes
                .First(md => md.IsAdditional)
                .Attributes;

            foreach (DictionaryEntry pair in additional)
            {
                var name = pair.Key as string;
                var value = pair.Value;

                var attribute = meta.SingleOrDefault(a => a.Name.IsEqual(name));
                if (attribute.IsNull())
                {
                    continue;
                }

                // ValueTypes
                if (!attribute.IsEntityReference)
                {
                    result.Add(new JProperty(name, value));
                    continue;
                }

                // IPersistent if exists
                var entity = value as IPersistentObject;
                if (entity.IsNull())
                {
                    continue;
                }

                var arrayName = PropertyInfo.Name;

                var innerProperties = AllInnerProperties
                    .Where(p => p.StartsWith(arrayName + '.'))
                    .Select(p => p.Remove(0, arrayName.Length).TrimStart('.'))
                    .OrderBy(p => p)
                    .ToList();

                var provider = new PersistentValueProvider(entity, name, innerProperties);
                var obj = provider.GetValue(target);

                if (obj.Is())
                {
                    result.Add(new JProperty(name, obj));
                }
            }

            return result;
        }
        #endregion Read

        #region Write
        public override void SetValue(object target, object value)
        {
            var jObj = value as JObject;

            var shouldBind = jObj.Is()
                             && PropertyInfo.Name.IsEqual("Additional")
                             && PropertyInfo.PropertyType.HasType<IDictionary>();

            if (!shouldBind)
            {
                return;
            }

            var additional = PropertyInfo.GetValue(target, null) as IDictionary;
            var properties = jObj.Properties().ToList();

            // the MetaData Attribute for current property
            var attribute = Attributes
                .First(md => md.IsAdditional);

            var canEdit = attribute.Is()
                          && attribute.CanEdit;

            if (!canEdit)
            {
                return;
            }

            BindAdditional(properties, additional, attribute);
        }

        protected virtual void BindAdditional(IList<JProperty> properties, IDictionary additional, MDAttribute attribute)
        {
            var attributes = attribute.Attributes;

            foreach (var jProperty in properties)
            {
                if (TryBindPersistent(attributes, additional, jProperty))
                {
                    continue;
                }

                var name = jProperty.Name;

                var attr = attributes
                    .FirstOrDefault(a => a.Name.Equals(name)
                                         && a.CanEdit);

                if (attr.IsNull() ||
                    attr.IsEntityReference)
                {
                    continue;
                }

                BindValueType(attr, additional, jProperty);
            }
        }

        protected void BindValueType(MDAttribute attr, IDictionary additional, JProperty jProperty)
        {
            var value = jProperty.TryGetValue(attr.Type);
            if (value.Is() || attr.IsNullable)
            {
                additional[attr.Name] = value;
            }
        }

        protected bool TryBindPersistent(IList<MDAttribute> attributes, IDictionary additional, JProperty jProperty)
        {
            var persitentName = jProperty.Name;

            if (persitentName.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase))
            {
                persitentName = persitentName.Substring(0, persitentName.Length - 2);
            }

            // try to find the IPersistentObject property
            var attr = attributes
                .FirstOrDefault(a => a.Name.Equals(persitentName)
                                     && a.Type.HasType<IPersistentObject>()
                                     && a.IsEntityReference);

            if (attr.IsNull())
            {
                return false;
            }

            if (!attr.CanEdit)
            {
                return true;
            }

            var idType = attr
                .Type
                .GetProperty(Str.Common.ID)
                .PropertyType;

            var id = jProperty.TryGetValue(idType);

            if (id.IsNull())
            {
                additional[attr.Name] = null;
                additional[attr.Name + "Id"] = null;
                return true;
            }

            IPersistentObject value;
            var wasBounded = TryGetPersistent<int>(attr.Type, id, out value)
                             || TryGetPersistent<string>(attr.Type, id, out value)
                             || TryGetPersistent<decimal>(attr.Type, id, out value)
                             || TryGetPersistent<long>(attr.Type, id, out value)
                             || TryGetPersistent<short>(attr.Type, id, out value)
                             || TryGetPersistent<uint>(attr.Type, id, out value)
                             || TryGetPersistent<ulong>(attr.Type, id, out value)
                             || TryGetPersistent<ushort>(attr.Type, id, out value)
                             || TryGetPersistent<byte>(attr.Type, id, out value)
                             || TryGetPersistent<Guid>(attr.Type, id, out value)
                ;

            if (wasBounded && value.Is())
            {
                additional[attr.Name] = value;
                additional[attr.Name + "Id"] = value.ID;
                return true;
            }

            PublishingManager.Publish(this, Severity.Error
                , "Failed to bind property '{0}' of type '{1}'. Not found item with passed ID [{2}] (see Json parameter: '{3}')"
                , attr.Name, attr.Type.Name, (jProperty.Value ?? string.Empty), jProperty.Name);

            return true;
        }
        protected virtual bool TryGetPersistent<TID>(Type type, object id, out IPersistentObject value)
        {
            value = null;
            var isKnwonIdType = id.Is()
                                && id.GetType().HasType<TID>();

            if (!isKnwonIdType)
            {
                return false;
            }

            var facade = type.GetFacade<TID>();
            value = facade.GetById((TID)id);
            return true;
        }
        #endregion Write
    }
}